﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using FuncWorks.XNA.XTiled;

namespace fess
{
  class PassMap
  {
    bool[,] passHoriz, passVert;
    Point size, tileSize;

    public PassMap(Map map)
    {
      size = new Point(map.Width, map.Height);
      tileSize = new Point(map.TileWidth, map.TileHeight);
      passHoriz = new bool[map.Width + 1, map.Height];
      passVert = new bool[map.Width, map.Height + 1];

      var passablityLayer = map.TileLayers["on the ground"];
      for(int x = 0; x < size.X; ++x)
        for (int y = 0; y < size.Y; ++y)
        {
          bool passable = passablityLayer.Tiles[x][y] == null;
          passHoriz[x, y] = passable && x > 0 && passablityLayer.Tiles[x - 1][y] == null;
          passVert[x, y] = passable && y > 0 && passablityLayer.Tiles[x][y - 1] == null;
        }
    }

    public Vector2 MoveObject(Vector2 position, Vector2 destination, float objectRadius)
    {
      Vector2 shift = destination - position;
      Point from = GetTile(position), to = GetTile(destination);
      float tBlocked = 1;
      bool posX = shift.X > 0;
      bool posY = shift.Y > 0;
      foreach (var current in BresenhamLine(from, to))
      {
        if (shift.X != 0)
        {
          int xc = current.X + (posX ? 1 : 0);
          float xBlocked = xc * tileSize.X + objectRadius * -Math.Sign(shift.X);
          float t = (xBlocked - position.X) * Math.Sign(shift.X) / Math.Abs(shift.X);
          if (!passHoriz[xc, current.Y])
            tBlocked = Math.Min(tBlocked, t);

          float y = position.Y + shift.Y * tBlocked;
          int yTileMax = Math.Min(size.Y - 1, (int)Math.Ceiling((y + objectRadius) / tileSize.Y) - 1);
          int yTileMin = Math.Max(0, (int)Math.Floor((y - objectRadius) / tileSize.Y));

          if (yTileMax > current.Y && !passHoriz[xc, current.Y + 1] || 
              yTileMin < current.Y && !passHoriz[xc, current.Y - 1])
            tBlocked = Math.Min(tBlocked, t);
        }

        if (shift.Y != 0)
        {
          int yc = current.Y + (posY ? 1 : 0);
          float yBlocked = yc * tileSize.Y + objectRadius * -Math.Sign(shift.Y);
          float t = (yBlocked - position.Y) * Math.Sign(shift.Y) / Math.Abs(shift.Y);
          if(!passVert[current.X, yc])
            tBlocked = Math.Min(tBlocked, t);
          
          float x = position.X + shift.X * tBlocked;
          int xTileMax = Math.Min(size.X - 1, (int)Math.Ceiling((x + objectRadius) / tileSize.X) - 1);
          int xTileMin = Math.Max(0, (int)Math.Floor((x - objectRadius) / tileSize.X));

          if (xTileMax > current.X && !passVert[current.X + 1, yc] ||
              xTileMin < current.X && !passVert[current.X - 1, yc])
            tBlocked = Math.Min(tBlocked, t);
        }

        if(tBlocked != 1)
          break;

        from = current;
      }

      return position + shift * Math.Max(0, tBlocked);
    }

    IEnumerable<Point> BresenhamLine(Point from, Point to)
    {
      int dx = Math.Abs(to.X - from.X);
      int dy = Math.Abs(to.Y - from.Y);
      int sx = from.X < to.X ? 1 : -1;
      int sy = from.Y < to.Y ? 1 : -1;
      int err = dx - dy;

      for (; ; )
      {
        yield return from;
        if (from.X == to.X && from.Y == to.Y)
          yield break;

        int e2 = 2 * err;
        if (e2 > -dy)
        {
          err = err - dy;
          from.X = from.X + sx;
        }

        if (e2 < dx)
        {
          err = err + dx;
          from.Y = from.Y + sy;
        }
      }
    }

    Point GetTile(Vector2 position)
    {
      return new Point((int)Math.Floor(position.X / tileSize.X), (int)Math.Floor(position.Y / tileSize.Y));
    }
  }
}
